<?php

namespace api\components;

use api\common\helpers\Constant;
use api\models\User;
use api\modules\app\models\Event;
use api\modules\app\models\EventCoopCreater;
use api\modules\app\models\EventDeadline;
use api\modules\app\models\EventDispatchTree;
use api\modules\app\models\EventFilterOuter;
use api\modules\app\models\EventOptInfo;
use api\modules\app\models\EventStatus;
use api\modules\app\models\OrderStep;
use api\modules\app\services\CountService;
use api\modules\app\services\ElasticSearchService;
use api\modules\app\services\EventFileService;
use api\modules\app\services\EventService;
use api\modules\app\services\UserService;
use api\modules\app\traits\TranslateTrait;
use api\modules\manage\components\Transfer;
use api\services\AreaService;
use api\utils\LogUtil;
use app\components\Job;
use Exception;
use Throwable;
use Yii;
use yii\helpers\ArrayHelper;

class FlowCenter
{
    use TranslateTrait;

    public $host;
    public $appId;
    public $appKey;

    const EVENT_INIT_CODE = 'OS-FLOW-000';

    const EVENT_LEVEL_TYPE_PLATFORM = 0;
    const EVENT_LEVEL_TYPE_DEPARTMENT = 1;
    const EVENT_LEVEL_TYPE_USER = 2;

    const EVENT_DISPATCH_DIRECTION_DEFAULT = 0;
    const EVENT_DISPATCH_DIRECTION_UP = 1;
    const EVENT_DISPATCH_DIRECTION_UNDER = 2;
    const EVENT_DISPATCH_DIRECTION_BACK = 3;
    const EVENT_DISPATCH_DIRECTION_REBACK = 4;

    const EVENT_DISPATCH_DONE = 0;
    const EVENT_DISPATCH_START = 1;
    const EVENT_DISPATCH_COOPER_STOP = 2;
    const EVENT_DISPATCH_COOP_FINISH = 3;
    const EVENT_DISPATCH_MAIN_WAIT_COOP = 4;
    const EVENT_DISPATCH_COOP_MAIN_WAIT_COOP = 5;
    const EVENT_DISPATCH_COOP_DELETED = 6;

    const EVENT_FLOW_COOPER_FIRST = 1;
    const EVENT_FLOW_MAIN_FIRST = 2;

    const EVENT_INIT_PATH = '/out-api/asycn-flow/new-case';
    const EVENT_RUN_PATH = '/out-api/asycn-flow/run-case';
    const EVENT_FORM_PATH = '/out-api/event-case-form/get-form';

    private function genSignParams()
    {
        $timestamp = time();
        return [
            'app_id' => $this->appId,
            'timestamp' => $timestamp,
            'sign' => md5($this->appId . $this->appKey . $timestamp),
        ];
    }

    public function getInitStatus(Event $event, $media = [])
    {
        $eventType = $event->event_type_code1;
        !empty($event->event_type_code2) && $eventType = $event->event_type_code2;
        !empty($event->event_type_code3) && $eventType = $event->event_type_code3;
        !empty($event->event_type_code4) && $eventType = $event->event_type_code4;
        $areaInfo = AreaService::getAreaCodeByMetaLevels($event);

        $params = [
            'case_code' => $event->event_id,
            'area' => $areaInfo['areaCode'],
            'area_level' => $areaInfo['level'],
            'case_source' => $event->source,
            'event_type' => $eventType,
            'discover_type' => $event->discover_type,
            'submit_type' => $event->submit_type ?? 1,
            'submit_uid' => $event->discover_user ?? '2',
//            'tag' => empty($event->tags) ? [] : explode(',', $event->tags),
        ];

        if(!empty($event->tags)){
            $params['tag'] = explode(',', $event->tags);
        }
        $params = array_merge($params, $this->genSignParams());
        // 请求装配
        $url = $this->host . self::EVENT_INIT_PATH;
        $response = Yii::$app->curl->get($url, $params);
        if ($response['code'] != '0') {
            throw new Exception($response['error']);
        }

        $condition = [
            'event_id' => $event->event_id,
            'coop_id' => '0',
            'code' => $event->code
        ];
        $transaction = Yii::$app->test_3->beginTransaction();
        try {
            $filter = EventFilterOuter::findOne($condition);
            if (!empty($filter)) {
                // 按键已经初始化过
                LogUtil::warn('Event has been inited', 'flow-center');
            } else {
                // 新一个没有 status 信息的 filter
                $filter = new EventFilterOuter($condition);
            }
            $filter->dispatching = self::EVENT_DISPATCH_START;
            $filter->request_id = $response['data']['callback_id'];
            $filter->save();
            $transaction->commit();
        } catch (Exception $e) {
            $transaction->rollback();
            LogUtil::warn(['message' => 'init case failed', 'e' => $e->getTraceAsString()], 'flow-center');
            throw $e;
        }

        $reportInfo = [
            'meta_level1' => $event->meta_level1,
            'meta_level2' => $event->meta_level2,
            'meta_level3' => $event->meta_level3,
            'meta_level4' => $event->meta_level4,
            'meta_level5' => $event->meta_level5,
            'department' => $event->department,
            'user_id' => $event->discover_user,
        ];

        $formatedForm = [];
        foreach ($media as $key => $value) {
            $formatedValue = [];
            if (is_array($value)) {
                foreach ($value as $item) {
                    if (!empty($item)) {
                        $formatedValue[] = $item;
                    }
                }
            } else {
                $formatedValue = $value;
            }
            switch ($key) {
                case 'images':
                    $type = 'image';
                    break;
                case 'videos':
                    $type = 'video';
                    break;
                case 'audios':
                    $type = 'audio';
                    break;
                case 'docs':
                    $type = 'doc';
                    break;
                default:
                    $type = 'file';
                    break;
            }
            $formatedForm[] = [
                'key' => $key,
                'value' => $formatedValue,
                'type' => $type,
            ];
        }

        $this->createOrderStep($filter,
                    ['id' => $event->discover_user, 'name' => $event->discover_username],
                    ['form' => $formatedForm],
                    $filter->request_id,
                    $reportInfo
                );

        try {
            // 同步到 es
            ElasticSearchService::upsert($filter->event_id);
        } catch (Exception $e) {
            LogUtil::warn(['message' => 'Insert into es filter failed', 'e' => $e->getTraceAsString()], 'flow-center');
        }

        return $filter;
    }

    public function dispatch(EventFilterOuter $filter, $opter, $requestParams = [])
    {
        switch ($filter->dispatching) {
            case self::EVENT_DISPATCH_START:
                $errorMsg = '案件正在流转中，请勿重复操作';
                break;
            case self::EVENT_DISPATCH_MAIN_WAIT_COOP:
            case self::EVENT_DISPATCH_COOP_MAIN_WAIT_COOP:
                $errorMsg = '请等待协办完成后进行流转';
                break;
            default:
                break;
        }
        if (!empty($errorMsg)) {
            throw new Exception($errorMsg);
        }
        $currInfo = EventService::getCurrentInfo($filter);
        $response = $this->sendRequest($filter, $currInfo, $requestParams, $opter);
        $action = $requestParams['action'];
        if (!empty($action['action_level_rule']['cooperation_rule']) && !empty($requestParams['coopers'])) { // 联勤联动
            $exitExt = empty($filter->ext_info) ? [] : json_decode($filter->ext_info, true);
            $coopConfigs = $exitExt['coopConfigs'] ?? [];
            $coopConfigs[$exitExt['dispatchInfo']['nodeId'] . ':' . $filter->status . ':' . $filter->coop_id] = [
                'cooperation_rule' => $action['action_level_rule']['cooperation_rule'],
                'cooperation_merge_node' => $action['action_level_rule']['cooperation_merge_node'],
            ];
            $exitExt['coopConfigs'] = $coopConfigs;
            $filter->ext_info = json_encode($exitExt);
        }
        $filter->dispatching = self::EVENT_DISPATCH_START;
        $filter->request_id = $response['data']['callback_id'];
        $orderStep = $this->createOrderStep($filter, $opter, $requestParams, $filter->request_id, $currInfo);
        $filter->save();

        if (!empty($action['action_level_rule']['cooperation_rule']) && !empty($requestParams['coopers'])) {
            $this->dispatchCooper($filter, $currInfo, $requestParams, $opter, $orderStep);
        }

        try {
            ElasticSearchService::upsert($filter->event_id);
        } catch (Exception $e) {
            LogUtil::warn(['message' => 'after dispatch update es filter failed', 'e' => $e->getTraceAsString()], 'flow-center');
        }

        return $filter;
    }

    private function sendRequest($filter, $currInfo, $requestParams, $opter, $isCoop = false, $index = 0)
    {
        $ext = json_decode($filter->ext_info, true);
        $params = [
            'case_code' => $filter->event_id,
            'children_case_code' => $filter->coop_id,
            'case_level' => [
                'code' => $filter->event_id,
                'area_code' => $currInfo['areaCode'],
                'level' => $currInfo['level'],
                'department' => $currInfo['department'],
                'user_id' => $currInfo['user_id'],
            ],
            'submit_uid' => $opter['id'],
            'action_form_content' => $requestParams['formatedForm'] ?? [],
            'status_node_id' => $ext['status']['id'] ?? '',
            'action_node_id' => $requestParams['action']['id'],
            'from' => $requestParams['from'] ?? [
                'type' => $currInfo['type'],
                'level' => $currInfo['level'],
            ],
            'to' => $this->prepareToParams(($isCoop ? $requestParams['coopers'] : $requestParams['to']), $requestParams['action'], $opter, $filter, $index),
            'deadline' => $requestParams['deadline'],
        ];

        if (!empty($ext['coopConfigs'])) {
            $mergeNodes = [];
            foreach ($ext['coopConfigs'] as $key => $value) {
                $mergeNodes[] = $value['cooperation_merge_node']['id'];
            }
            if (!empty($mergeNodes)) {
                $params['cooperation_merge_nodes'] = $mergeNodes;
            }
        }

        $params = array_merge($params, $this->genSignParams());
        // 请求装配
        $url = $this->host . self::EVENT_RUN_PATH;
        $response = Yii::$app->curl->post($url, $params);
        if ($response['code'] != '0') {
            throw new Exception($response['error']);
        }

        return $response;
    }

    private function prepareToParams($to, $action, $opter, $filter, $index = 0)
    {
        switch ($action['action_level_to']) {
            case self::EVENT_DISPATCH_DIRECTION_BACK:
                return $action['back'][$index]['to'] ?? [];
            case self::EVENT_DISPATCH_DIRECTION_REBACK:
                return $this->getRebackInfoByOpter($action, $opter, $filter);
            default:
                return $to[$index] ?? [];
        }
    }

    private function dispatchCooper(EventFilterOuter $filter, $currInfo, $requestParams, $opter, $orderStep)
    {
        $coopers = $this->genCoopers($filter, $currInfo, count($requestParams['coopers']));
        $index = 0;
        foreach ($coopers as $cooper) {
            $newFilter = new EventFilterOuter($filter);
            unset($newFilter['id']);
            $newFilter->setAttributes([
                'code' => $cooper['code'],
                'coop_id' => strval($cooper['id']),
                'is_coop' => 1,
                'dispatching' => 1,
                'route' => $filter->meta_id,
            ]);
            $response = $this->sendRequest($newFilter, $currInfo, $requestParams, $opter, true, $index);
            $newFilter->dispatching = self::EVENT_DISPATCH_START;
            $newFilter->request_id = $response['data']['callback_id'];
            $this->createCooperStep($orderStep, $newFilter);
            $newFilter->save();
            $index++;
        }
    }

    private function createCooperStep(OrderStep $orderStep, EventFilterOuter $filter)
    {
        $cooperStep = new OrderStep($orderStep);
        unset($cooperStep['id']);
        $extInfo = json_decode($orderStep->ext_data, true);
        $cooperStep->ext_data = json_encode(array_merge($extInfo, ['parentId' => $orderStep->id]));
        $cooperStep->coop_id = $filter->coop_id;
        $cooperStep->unique_id = $filter->request_id;
        $cooperStep->save();
    }

    private function genCoopers(EventFilterOuter $filter, $currentInfo, $count)
    {
        $extInfo = json_decode($filter->ext_info, true);
        $item = [
            'event_id' => $filter->event_id,
            'code' => '',
            'node_id' => $extInfo['dispatchInfo']['nodeId'],
            'status' => $filter->status,
            'parent_id' => intval($filter->coop_id),
        ];
        for ($i = 0; $i < $count; $i++) {
            $items[] = array_merge($item, ['code' => EventService::generateCode($currentInfo)]);
        }

        Yii::$app->test_3->createCommand()
                     ->batchInsert(EventCoopCreater::tableName(), array_keys($item), $items)
                     ->execute();

        return EventCoopCreater::find()->where([
                                            'event_id' => $filter->event_id,
                                            'status' => $filter->status,
                                            'parent_id' => intval($filter->coop_id),
                                        ])->orderBy(['id' => SORT_DESC])->limit($count)->all();
    }

    private function getRebackInfoByOpter($action, $opter, EventFilterOuter $filter)
    {
        $condition = UserService::getUserPlatformCondition($opter);
        $backNode = EventDispatchTree::find()->where(array_merge([
                                                'event_id' => $filter->event_id,
                                                'coop_id' => $filter->coop_id,
                                                'id' => $action['reback']['parentIds'],
                                            ], $condition))
                                            ->orderBy(['id' => SORT_DESC])
                                            ->limit(1)
                                            ->one();
        if (empty($backNode)) {
            LogUtil::warn(['user' => $condition, 'action' => $action, 'message' => 'get reback to failed'], 'flow-center');
            throw new Exception('案件已被收回，或无权收回案件');
        }
        return [
            'type' => $backNode['type'],
            'area' => $backNode['area_code'],
            'level' => $backNode['level'],
            'department' => $backNode['department'],
            'user_id' => $backNode['user_id'],
        ];
    }

    public function handleFeedBack($requestId, $response)
    {
        $filter = EventFilterOuter::findOne(['request_id' => $requestId]);
        $tryTimes = 0;
        while (empty($filter->dispatching) && $tryTimes < 3) {
            sleep(1);
            $filter = EventFilterOuter::findOne(['request_id' => $requestId]);
            $tryTimes++;
        }
        if ($response['code'] == 1001) {
            // LogUtil::warn(['message' => 'dispatch handle error', 'response' => $response], 'flow-center');
            // $filter->dispatching = 0;
            // $filter->save();
            // try {
            //     // 更新 es：放在 beforeSave ?
            //     ElasticSearchService::update($filter->id, [EventFilterOuter::tableName() => $filter->toArray()]);
            // } catch (Exception $e) {
            //     LogUtil::warn(['message' => 'handel error feedback update es filter failed', 'e' => $e->getTraceAsString()], 'flow-center');
            // }
            return true;
        }
        if (!$filter->dispatching) {
            LogUtil::warn('event isnt dispatching', 'flow-center');
            return true;
        }
        $transaction = Yii::$app->test_3->beginTransaction();
        $currNode = EventService::getCurrentDispatchNode($filter);
        try {
            $this->handleDispatch($filter, $currNode, $response);
            $transaction->commit();
        } catch (Exception $e) {
            $transaction->rollback();
            LogUtil::warn([
                'message' => 'handle feedback error',
                'e' => $e->getTraceAsString(),
                'eventId' => $filter->event_id,
            ], 'flow-center');
            throw $e;
        }
        EventFileService::updateStatus($filter->event_id, EventFileService::FILE_STATUS_USEFUL);
        try {
            ElasticSearchService::upsert($filter->event_id);
        } catch (Exception $e) {
            LogUtil::warn(['message' => 'after feedback update es filter failed', 'eventId' => $filter->event_id, 'e' => $e->getTraceAsString()], 'flow-center');
        }
        Transfer::push2StepQueue($requestId);
        $this->handleDealline($filter, $response);
        return $filter;
    }

    private function handleDealline(EventFilterOuter $filter, $response)
    {
        $this->handleDeallineEnd($filter, $response);
        if (!empty($response['flow_case']['timelimit_config'])) {
            $this->handleDeallineStart($filter, $response);
        }
    }

    private function handleDeallineStart(EventFilterOuter $filter, $data)
    {
        $rules = $data['flow_case']['timelimit_config'];
        $extInfo = json_decode($filter->ext_info, true);
        $dispatchInfo = $extInfo['dispatchInfo'];
        foreach ($rules as $rule) {
            $exits = EventDeadline::findAll([
                'event_id' => $filter->event_id,
                'coop_id' => $filter->coop_id,
                'unique_id' => $rule['key']
            ]);
            if (!empty($exits)) {
                continue;
            }
            $deadline = new EventDeadline([
                'event_id' => $filter->event_id,
                'coop_id' => $filter->coop_id,
                'type' => 1,
                'unique_id' => $rule['key'],
                'deadline' => date('Y-m-d H:i:s', $rule['timeout']),
                'ext' => json_encode($rule),
                'node_id' => $dispatchInfo['nodeId']
            ]);
            $deadline->save();
        }
    }

    private function handleDeallineEnd(EventFilterOuter $filter, $response)
    {
        $rules = EventDeadline::findAll([
            'event_id' => $filter->event_id,
            'coop_id' => $filter->coop_id,
            'type' => 1,
            'state' => 0,
        ]);
        if (empty($rules)) {
            return;
        }
        $currStatus = $response['flow_case']['status'];
        foreach ($rules as $rule) {
            if (empty($rule->ext)) {
                continue;
            }
            $ext = json_decode($rule->ext, true);
            if (in_array($currStatus['id'], $ext['stop_node'])) {
                $rule->finish_time = date('Y-m-d H:i:s', time());
                $rule->state = 1;
                $rule->save();
            }
        }
    }

    public function handleDispatch(EventFilterOuter $filter, ?EventDispatchTree $currNode, $response = [], $backNode = [])
    {
        $preActions = [];
        $actions = !empty($filter->next_actions) ? json_decode($filter->next_actions, true) : [];
        foreach ($actions as $item) {
            $preActions[$item['action_code']] = $item;
        }
        $flow = $response['flow_case'];
        $logs = $response['log'] ?? [];
        $route = empty($filter->route) ? [] : explode(',', $filter->route);
        $extInfo = !empty($filter->ext_info) ? json_decode($filter->ext_info, true) : [];
        $extInfo['status'] = $flow['status'];
        if (empty($extInfo['all_status'])) {
            foreach ($response['all_status'] as $statusId => $status) {
                $extInfo['all_status'][$status['stage_code']][] = $status['status_code'];
            }
        }

        if (empty($backNode)) {
            // 案件层级
            $metaInfo = AreaService::getMetaLevelsByCode($flow['area']['code']);
            $metaInfo['level'] = $flow['area']['level'] ?? 0;
            $metaInfo['department'] = strval(empty($flow['department']) ? '0' : $flow['department']);
            $metaInfo['user_id'] = intval($flow['user'] ?? 0);
            $metaInfo['type'] = self::EVENT_LEVEL_TYPE_PLATFORM;
            if (!empty($metaInfo['department'])) {
                $metaInfo['type'] = self::EVENT_LEVEL_TYPE_DEPARTMENT;
            }
            if (!empty($metaInfo['user_id'])) {
                $metaInfo['type'] = self::EVENT_LEVEL_TYPE_USER;
            }
        } else {
            $metaInfo = AreaService::getMetaLevelsByCode($backNode['area_code']);
            $metaInfo = array_merge($metaInfo, [
                'level' => $backNode['level'],
                'type' => $backNode['type'],
                'department' => $backNode['department'],
                'user_id' => $backNode['user_id'],
            ]);
        }
        $logCount = 0;
        foreach ($logs as $log) {
            $toArea = !empty($backNode) ? $backNode['area_code'] : $log['to_area']['code'];
            $toAreaLevel = !empty($backNode) ? $backNode['level'] : $log['to_area']['level'];
            $toDepartment = !empty($backNode) ? $backNode['department'] : $log['to_department'];
            $toUser = !empty($backNode) ? $backNode['user_id'] : $log['to_user'];
            // 流转层级
            $dispatchInfo = [
                'area_code' => $toArea,
                'level' => $toAreaLevel,
                'type' => '0',
                'department' => '0',
                'user_id' => '0',
                'deadline' => $log['request_log']['deadline'] ?? null,
            ];
            if (!empty($toDepartment)) {
                $dispatchInfo['type'] = self::EVENT_LEVEL_TYPE_DEPARTMENT;
                $dispatchInfo['department'] = strval(empty($toDepartment) ? '0' : $toDepartment);
            }
            if (!empty($toUser)) {
                $dispatchInfo['type'] = self::EVENT_LEVEL_TYPE_USER;
                $dispatchInfo['user_id'] = intval(empty($toUser) ? 0 : $toUser);
            }
            $route = $this->updateRoute($route, $dispatchInfo);
            $action = empty($log['action_code']) ? self::EVENT_INIT_CODE : $log['action_code'];
            $opter = [
                'id' => $log['action_user'],
                'username' => $log['is_auto'] ? 'auto_system' : $this->getUserName($log['action_user']),
            ];
            $this->updateFilter($filter, $action, $log['from_status'], $log['to_status']);
            // 插入当前层级状态
            $eventStatus = $this->upsertEventStatus($filter, $metaInfo);
            $currAction = $preActions[$action] ?? [];
            if (empty($backNode) && empty($currAction['back']) && empty($currAction['reback'])) {
                // 插入流转节点
                $nextNode = $this->insertTreeNode($filter, $currNode, $dispatchInfo);
            } else {
                // 回退。收回 操作，删除节点
                if (empty($backNode)) {
                    $backNode = EventDispatchTree::find()->where([
                                                            'event_id' => $filter->event_id,
                                                            'coop_id' => $filter->coop_id,
                                                            'status' => $filter->status,
                                                            'area_code' => $dispatchInfo['area_code'],
                                                            'level' => $dispatchInfo['level'],
                                                            'type' => $dispatchInfo['type'],
                                                            'department' => $dispatchInfo['department'],
                                                            'user_id' => $dispatchInfo['user_id'],
                                                        ])->orderBy(['id' => SORT_DESC])->limit(1)->one();
                }
                if (empty($backNode)) {
                    LogUtil::warn([
                        'message' => 'get empty back node',
                        'action' => $action,
                        'dispatchInfo' => $dispatchInfo,
                        'event_id' => $filter->event_id,
                        'coop_id' => $filter->coop_id,
                    ], 'flow-center');
                }
                $delNodeIds = [];
                EventService::getChildNodeIds($backNode['id'], $delNodeIds);
                $delNodes = EventDispatchTree::findAll(['id' => $delNodeIds]);
                if ($delNodes) {
                    foreach ($delNodes as $node) {
                        $metas[] = empty($node->department) ? $node->area_code : $node->department;
                    }
                    $route = array_values(array_diff($route, $metas));
                }
                EventDispatchTree::updateAll(['is_deleted' => 1], ['id' => $delNodeIds]);
                EventDeadline::deleteAll(['node_id' => $delNodeIds]);
                $delCoopNodeIds = array_merge([$backNode['id']], $delNodeIds);
                $delCoopCondtion = [
                    'event_id' => $filter->event_id,
                    'node_id' => $delCoopNodeIds,
                    'is_deleted' => 0,
                ];
                $delCoopers = EventCoopCreater::findAll($delCoopCondtion);
                if (!empty($delCoopers)) { // 将子节点里的协办全部删除
                    EventFilterOuter::updateAll(
                        ['dispatching' => self::EVENT_DISPATCH_COOP_DELETED],
                        ['event_id' => $filter->event_id, 'is_coop' => 1, 'coop_id' => ArrayHelper::getColumn($delCoopers, 'id')]
                    );
                    EventCoopCreater::updateAll(['is_deleted' => 1], $delCoopCondtion);
                }
                $this->handleCooperBack($filter, $backNode, $response);
            }

            $stepInfo = [
                'step_status' => $action,
                'ext_info' => json_encode($log['request_log']['action_form_content'] ?? []),
            ];
            // 插入状态描述信息
            $this->upsertOptInfo($filter, $opter, $stepInfo);
            // 更新操作步骤
            $this->upsertStep($filter, $opter, $eventStatus, $log, $logCount);
            $currNode = $nextNode ?? $backNode;
            $logCount++;
            $this->sendNotification($log, $currAction, $dispatchInfo);
        }

        // 需要合并主协节点
        if (!empty($extInfo['coopConfigs'])) {
            $needMergeNodes = [];
            $updatedCoopConfigs = [];
            $coopChanged = false;
            foreach ($extInfo['coopConfigs'] as $key => $config) {
                // 到达了结束节点
                if (!empty($config['cooperation_rule']) && $flow['status']['id'] == $config['cooperation_merge_node']['id']) {
                    list($startNodeId, $baginStatus, $coopId) = explode(':', $key);
                    if ($coopId != $filter->coop_id) { // 该分叉处的协办
                        $cooper = EventCoopCreater::findOne(['id' => $filter->coop_id, 'parent_id' => $coopId]);
                        if (empty($cooper)) { // 当前节点祖父节点的合并信息，不需要处理
                            continue;
                        }
                    }
                    if (!empty($delCoopNodeIds) && in_array($startNodeId, $delCoopNodeIds)) {
                        continue;
                    }
                    $updatedCoopConfigs[$key] = $config;
                    $coopChanged = true;
                    $needMergeNodes[$startNodeId] = [
                        'baginStatus' => $baginStatus,
                        'coopId' => $coopId,
                        'config' => $config,
                    ];
                }
            }
            if (!empty($needMergeNodes)) {
                ksort($needMergeNodes, SORT_DESC); // 优先处理子分叉
                foreach ($needMergeNodes as $nodeId => $info) {
                    $this->mergeCoopers($filter, $nodeId, $info['config'], $info['baginStatus'], $info['coopId']);
                }
            }
        }

        $extInfo['dispatchInfo'] = $dispatchInfo;
        if ($coopChanged) {
            $extInfo['coopConfigs'] = $updatedCoopConfigs;
        }
        $extInfo['dispatchInfo']['nodeId'] = $currNode['id'];
        $this->updateNextActions($filter, $eventStatus, $currNode, $flow, $extInfo, $route);
        try {
            CountService::updateByFilter($filter);
        } catch (Throwable $e) {
            LogUtil::warn(['filter' => $filter->toArray(), 'message' => 'update filter count failed', 'e' => $e->getTraceAsString()], 'flow-center');
        }
    }

    private function handleCooperBack(EventFilterOuter $filter, $backNode, $response)
    {
        $coopers = EventCoopCreater::findAll([
            'event_id' => $filter->event_id,
            'parent_id' => $filter->coop_id,
            'is_deleted' => 0,
            'state' => 1
        ]);
        if (empty($coopers)) {
            // 只处理已合并的协办
            return ;
        }
        $coopNodes = EventDispatchTree::findAll([
            'level' => $backNode['level'],
            'type' => $backNode['type'],
            'status' => $backNode['status'],
            'event_id' => $filter->event_id,
            'coop_id' => ArrayHelper::getColumn($coopers, 'id'),
        ]);
        foreach ($coopNodes as $coopBackNode) {
            $cooperFilter = EventFilterOuter::findOne(['event_id' => $filter->event_id, 'coop_id' => $coopBackNode['coop_id']]);
            $this->handleDispatch($cooperFilter, null, $response, $coopBackNode);
        }
    }

    private function isMergeStatus(EventFilterOuter $filter, $config)
    {
        $extInfo = json_decode($filter->ext_info, true);
        if ($extInfo['status']['id'] == $config['cooperation_merge_node']['id']) {
            return true;
        }
        return false;
    }

    private function mergeCoopers(EventFilterOuter $filter, $nodeId, $rule, $baginStatus, $parentCoopId)
    {
        if ($filter->coop_id != $parentCoopId) { // 当前案件是协办
            $this->handleCooperFinish($filter, $nodeId, $rule, $parentCoopId);
        } else {
            $this->handleMainFinish($filter, $nodeId, $rule, $parentCoopId);
        }
    }

    private function handleCooperFinish(EventFilterOuter $filter, $nodeId, $rule, $parentCoopId)
    {
        $filter->dispatching = self::EVENT_DISPATCH_COOP_FINISH;
        if ($rule['cooperation_rule'] == self::EVENT_FLOW_MAIN_FIRST) {
            // 协办，主责优先情况下直接完成。
            $filter->save();
            return $filter;
        }
        $mainFilter = EventFilterOuter::findOne(['event_id' => $filter->event_id, 'coop_id' => $parentCoopId]);
        $isMainFinish = $this->isMergeStatus($mainFilter, $rule);
        if (!$isMainFinish) {
            $filter->save();
            return $filter;
        }

        $otherCoopers = EventCoopCreater::find()->where(['node_id' => $nodeId, 'is_deleted' => 0])
                                                ->andWhere(['!=', 'id', $filter->coop_id])->all();
        $allCooperFilters = !empty($otherCoopers) ? EventFilterOuter::findAll(['event_id' => $filter->event_id, 'coop_id' => ArrayHelper::getColumn($otherCoopers, 'id')]) : [];
        $allCooperFinished = true;
        foreach ($allCooperFilters as $cooperFilter) {
            if (!$this->isMergeStatus($cooperFilter, $rule)) {
                $allCooperFinished = false;
            }
        }

        if ($allCooperFinished) {
            $mainFilter->dispatching = self::EVENT_DISPATCH_DONE; // 主协都完成，正常向下流转
            EventCoopCreater::updateAll(['state' => 1], ['event_id' => $mainFilter->event_id, 'parent_id' => $mainFilter->coop_id]);
        } else {
            $mainFilter->dispatching = empty($mainFilter->coop_id) ? self::EVENT_DISPATCH_MAIN_WAIT_COOP : self::EVENT_DISPATCH_COOP_MAIN_WAIT_COOP;
        }
        $mainFilter->save();
    }

    private function handleMainFinish(EventFilterOuter $filter, $nodeId, $rule, $parentCoopId)
    {
        $coopers = EventCoopCreater::findAll(['node_id' => $nodeId, 'is_deleted' => 0]);
        if ($rule['cooperation_rule'] == self::EVENT_FLOW_MAIN_FIRST) { // 主责优先
            EventFilterOuter::updateAll(['dispatching' => self::EVENT_DISPATCH_COOPER_STOP], [
                'event_id' => $filter->event_id,
                'coop_id' => ArrayHelper::getColumn($coopers, 'id'),
                // 'dispatching' => [self::EVENT_DISPATCH_START, self::EVENT_DISPATCH_DONE], // 正常流转中的协办修改为停止流转
            ]);
            EventCoopCreater::updateAll(['state' => 1], ['event_id' => $filter->event_id, 'parent_id' => $filter->coop_id]);
            return $filter;
        }
        // 主协同责
        $finishedCooper = EventFilterOuter::findAll([
            'event_id' => $filter->event_id,
            'coop_id' => ArrayHelper::getColumn($coopers, 'id'),
            'dispatching' => self::EVENT_DISPATCH_COOP_FINISH,
        ]);
        if (count($finishedCooper) != count($coopers)) {
            $filter->dispatching = empty($filter->coop_id) ? self::EVENT_DISPATCH_MAIN_WAIT_COOP : self::EVENT_DISPATCH_COOP_MAIN_WAIT_COOP;
        } else {
            $filter->dispatching = empty($filter->coop_id) ? self::EVENT_DISPATCH_START : self::EVENT_DISPATCH_COOP_FINISH;
            EventCoopCreater::updateAll(['state' => 1], ['event_id' => $filter->event_id, 'parent_id' => $filter->coop_id]);
        }
        $filter->save();
    }

    /**
     * @todo push job 队列
     */
    private function sendNotification($log, $action, $dispatchInfo)
    {
        $class = 'App\\Job\\SendNotification';
        $args = [
            'log' => $log,
            'action' => $action,
            'dispatchInfo' => $dispatchInfo,
        ];
        (new Job())->create($class, $args);
    }

    public function updateRoute($route, $info)
    {
        $code = $info['department'];
        if (empty($code)) {
            $code = $info['area_code'];
        }
        if (!empty($code) && !in_array($code, $route)) {
            $route[] = $code;
        }

        return $route;
    }

    public function updateFilter(EventFilterOuter $filter, $action, $from, $to)
    {
        $filter->setAttributes([
            'status' => $to['status_code'],
            'period_status' => $to['stage_code'],
            'status_time' => date('Y-m-d H:i:s', time()) ,
            'step_status' => $action,
            'pre_status' => $from['status_code'] ?? '',
            'pre_period_status' => $from['stage_code'] ?? '',
        ]);
        $filter->save();
    }

    /**
     * 更新最终支持的 actions 和 status
     */
    private function updateNextActions(EventFilterOuter $filter, $eventStatus, $currNode, $flow = [], $extInfo = [], $route = [])
    {
        $nextActions = [];
        $actions = empty($flow['action']) ? [] : $flow['action'];
        foreach ($actions as $actionId => $action) {
            $formated = $this->formatActionData($eventStatus, $action, $currNode);
            if (!empty($formated)) {
                $nextActions[] = $formated;
            }
        }
        $filter->ext_info = json_encode($extInfo);
        $filter->next_actions = json_encode($nextActions);
        if ($filter->dispatching == self::EVENT_DISPATCH_START) {
            $filter->dispatching = self::EVENT_DISPATCH_DONE;
        }
        $filter->route = implode(',', $route);
        $filter->meta_id = empty($currNode['department']) ? $currNode['area_code'] : $currNode['department'];
        $filter->save();
    }

    /**
     * 根据案件当前层级信息，格式化前端需要的结构
     */
    private function formatActionData($currInfo, $action, $currNode)
    {
        $rules = $action['action_level_rule']['rule_list'] ?? [];
        if ($action['action_level_to'] == self::EVENT_DISPATCH_DIRECTION_DEFAULT) {
            $action['matchedRules'] = $rules;
            return $action;
        }
        if ($action['action_level_to'] == self::EVENT_DISPATCH_DIRECTION_REBACK) {
            $action['reback'] = [
                'currentId' => $currNode['id'],
                'parentIds' => explode(',', $currNode['parent_ids']),
            ];
            $action['matchedRules'] = [[
                'from' => [],
                'to' => [],
            ]];
            return $action;
        }

        $matchedRules = [];
        foreach ($rules as $rule) {
            if ($rule['from']['type'] != $currInfo['type'] || $rule['from']['level'] != $currInfo['level']) {
                continue;
            }
            $matchedRules[] = $rule;
            if ($action['action_level_to'] == self::EVENT_DISPATCH_DIRECTION_UP) {
                $action['up'][] = $this->getUpMetaInfo($currInfo, $rule);
            }
            if ($action['action_level_to'] == self::EVENT_DISPATCH_DIRECTION_BACK) {
                $action['back'][] = $this->getBackInfo($currInfo, $rule, $currNode);
            }
        }
        $action['matchedRules'] = $matchedRules;

        return $action;
    }

    private function getUpMetaInfo($currInfo, $rule)
    {
        $toRule = $rule['to'];
        $level = $toRule['level'][0];
        $area = $currInfo['meta_level' . ($level + 1)];
        $type = self::EVENT_LEVEL_TYPE_PLATFORM;
        if (in_array(self::EVENT_LEVEL_TYPE_DEPARTMENT, $toRule['type'])) {
            $department = $currInfo['department'];
            $type = self::EVENT_LEVEL_TYPE_DEPARTMENT;
        }
        $upInfo = [
            'type' => $type,
            'area' => $area,
            'areaName' => $this->translateCode($area),
            'level' => $level,
            'department' => $department ?? '0',
            'departmentName' => empty($department) ? '' : $this->translateCode($department, 'department'),
        ];
        return [
            'to' => $upInfo,
        ];
    }

    private function getBackInfo($currInfo, $rule, $currNode)
    {
        $toRule = $rule['to'];
        $parentIds = explode(',', $currNode['parent_ids']);
        $node = EventDispatchTree::find()->where([
                                            'id' => $parentIds,
                                            'type' => $toRule['type'],
                                            'level' => $toRule['level'],
                                            'is_deleted' => 0,
                                        ])->orderBy(['id' => SORT_DESC])->limit(1)->one();

        $backInfo = [
            'type' => $node['type'],
            'level' => $node['level'],
            'area' => $node['area_code'],
            'areaName' => $this->translateCode($node['area_code']),
            'department' => $node['department'],
            'departmentName' => empty($node['department']) ? '' : $this->translateCode($node['department'], 'department'),
            'user_id' => $node['user_id'],
        ];
        return [
            'to' => $backInfo,
            'backNodeId' => $node['id'],
        ];
    }

    /**
     * eventId + coopId + status 唯一：某一状态案件只能在一个单位
     */
    public function upsertEventStatus($filter, $metaInfo)
    {
        $mainEs = EventStatus::findOne([
            'event_id' => $filter->eventId,
            'status' => $filter->status,
            'coop_id' => $filter->coopId ?? '0',
        ]);
        if (empty($mainEs)) {
            $mainEs = new EventStatus([
                'event_id' => $filter->eventId,
                'status' => $filter->status,
                'coop_id' => $filter->coop_id ?? '0',
            ]);
        } else {
            $mainEs->has_dispatch = true;
        }
        $mainEs->is_coop = empty($coopId) ? 0 : 1;
        $mainEs->setAttributes($metaInfo);
        $mainEs->save();

        return $mainEs;
    }

    /**
     * 创建派遣树记录： eventId + coopId + status + dispatchInfo(area/department/userId) 唯一
     */
    public function insertTreeNode(EventFilterOuter $filter, ?EventDispatchTree $preNode, $dispatchInfo)
    {
        $node = new EventDispatchTree([
            'event_id' => $filter->eventId,
            'coop_id' => $filter->coopId ?? '0',
            'is_coop' => empty($filter->coop_id) ? 0 : 1,
            'status' => $filter->status,
            'parent_id' => $preNode->id ?? 0,
            'parent_ids' => empty($preNode) ? '0' : $preNode->parent_ids . ',' . $preNode->id,
            'area_code' => $dispatchInfo['area_code'],
            'level' => $dispatchInfo['level'],
            'type' => $dispatchInfo['type'] ?? 0,
            'department' => $dispatchInfo['department'] ?? '0',
            'user_id' => $dispatchInfo['user_id'] ?? 0,
        ]);
        if (!empty($dispatchInfo['deadline'])) {
            $node->deadline = is_numeric($dispatchInfo['deadline']) ? date('Y-m-d H:i:s', $dispatchInfo['deadline']) : $dispatchInfo['deadline'];
        }
        $node->save();

        return $node;
    }

    public function upsertOptInfo(EventFilterOuter $filter, $opter, $params = [])
    {
        $optInfo = EventOptInfo::findOne(['event_id' => $filter->eventId, 'coop_id' => $filter->coop_id ,'step_status' => $params['step_status']]);
        if (empty($optInfo)) {
            $optInfo = new EventOptInfo(['event_id' => $filter->eventId, 'coop_id' => $filter->coop_id, 'step_status' => $params['step_status']]);
        }
        $optInfo->setAttributes($params);
        $optInfo->opt_user_id = $opter['id'];
        $optInfo->opt_username = $opter['username'];
        $optInfo->save();
    }

    public function createOrderStep(EventFilterOuter $filter, $opter, $requestParams, $uniqueId = '', $currInfo = [])
    {
        $cacheData = [];
        $fromInfo = $this->formatDispatchAreaInfo($currInfo, 'from', $cacheData);
        $ext = array_merge($fromInfo, [ // 记录快照
            'opt_user_id' => $opter['id'] ?? 0,
            'opt_username' => $opter['username'] ?? 'system',
            'step_status' => $requestParams['action']['action_code'] ?? '',
            'step_status_name' => $requestParams['action']['name'] ?? '',
            'fromStatus' => $filter->status ?? '',
            'fromPeriodStatus' => $filter->period_status ?? '',
            'coopers' => !empty($requestParams['coopers']) ? $requestParams['coopers'] : [],
            'main' => $requestParams['to'],
            'deadline' => $requestParams['deadline'] ?? null,
        ]);
        if (empty($uniqueId)) {
            $uniqueId = uniqid();
        }

        $orderStep = new OrderStep([
            'unique_id' => $uniqueId,
            'event_id' => $filter->event_id,
            'coop_id' => $filter->coop_id,
            'opt_user_id' => $opter['id'],
            'step_status' => $requestParams['action']['action_code'],
            'status' => $filter->status,
            'period_status' => $filter->period_status,
            'ext_data' => json_encode($ext),
        ]);
        $orderStep->form = json_encode([
            'formId' => $requestParams['formId'] ?? '',
            'formData' => $requestParams['form'],
        ]);
        $orderStep->save();
        return $orderStep;
    }

    /**
     * 记录快照，需要在提交时就记录，流程返回后更新
     */
    public function upsertStep(EventFilterOuter $filter, $opter, $eventStatus, $log, $index = 0)
    {
        if ($index == 0) {
            $orderStep = OrderStep::findOne(['unique_id' => $filter->request_id]);
        }
        if (empty($orderStep)) {
            $orderStep = new OrderStep([
                'unique_id' => empty($index) ? $filter->request_id : $filter->request_id . '_' . $index,
            ]);
            LogUtil::warn(['message' => 'order_step not found', 'unique_id' => $filter->request_id], 'flow-center');
        }
        $form = json_decode($orderStep->form, true);
        if (!empty($form['formId'])) {
            $formInfo = $this->getForm($form['formId']);
            $formId = $formInfo['form']['name'] ?? '';
            $orderStep->form = json_encode([
                'formId' => $formId ?? '',
                'formData' => $form['formData'],
            ]);
        }
        $ext = [];
        if (!empty($orderStep->ext_data)) {
            $ext = json_decode($orderStep->ext_data, true) ?? [];
        }
        $statusInfo = [
            'fromStatus' => $log['from_status']['status_code'] ?? '',
            'fromPeriodStatus' => $log['from_status']['stage_code'] ?? '',
            'toStatus' => $log['to_status']['status_code'] ?? '',
            'toPeriodStatus' => $log['to_status']['stage_code'] ?? '',
        ];
        $cacheData = [];
        $toAreaInfo = $this->formatDispatchAreaInfo($eventStatus, 'to', $cacheData);
        if (!empty($ext['coopers'])) {
            $ext['coopers'] = $this->formatDispatchMetaInfo($ext['coopers'], $cacheData);
            $ext['main'] = $this->formatDispatchMetaInfo($ext['main'], $cacheData);
        }
        $newExt = array_merge($ext, $toAreaInfo, $statusInfo);
        // 插入 order_step
        $orderStep->setAttributes([
            'event_id' => $filter->event_id,
            'coop_id' => $filter->coop_id,
            'opt_user_id' => $opter['id'],
            'step_status' => empty($log['action_code']) ? self::EVENT_INIT_CODE : $log['action_code'],
            'pre_status' => $log['from_status']['status_code'],
            'pre_period_status' => $log['from_status']['stage_code'],
            'status' => $log['to_status']['status_code'],
            'period_status' => $log['to_status']['stage_code'],
            'ext_data' => json_encode($newExt),
        ]);
        $orderStep->save();
    }

    private function formatDispatchMetaInfo($coopers, $cacheData)
    {
        $formatedCoop = [];
        foreach ($coopers as $cooper) {
            $areaName = $this->translateCode($cooper['area'], 'area', $cacheData);
            $departmentName = $this->translateCode($cooper['department'], 'department', $cacheData);
            $userName = $this->getUserName($cooper['user_id']);
            $formatedCoop[] = array_merge($cooper, ['areaName' => $areaName, 'departmentName' => $departmentName, 'userName' => $userName]);
        }
        return $formatedCoop;
    }

    private function formatDispatchAreaInfo($metaInfo, $perfix = 'from', &$data)
    {
        if (empty($metaInfo)) {
            return [];
        }
        return [
            $perfix . '_meta_level1' => $metaInfo['meta_level1'],
            $perfix . '_meta_level1_name' => $this->translateCode($metaInfo['meta_level1'], 'area', $data),
            $perfix . '_meta_level2' => $metaInfo['meta_level2'],
            $perfix . '_meta_level2_name' => $this->translateCode($metaInfo['meta_level2'], 'area', $data),
            $perfix . '_meta_level3' => $metaInfo['meta_level3'],
            $perfix . '_meta_level3_name' => $this->translateCode($metaInfo['meta_level3'], 'area', $data),
            $perfix . '_meta_level4' => $metaInfo['meta_level4'],
            $perfix . '_meta_level4_name' => $this->translateCode($metaInfo['meta_level4'], 'area', $data),
            $perfix . '_meta_level5' => $metaInfo['meta_level5'],
            $perfix . '_meta_level5_name' => $this->translateCode($metaInfo['meta_level5'], 'area', $data),
            $perfix . '_department' => $metaInfo['department'] ?? '0',
            $perfix . '_department_name' => empty($metaInfo['department']) ? '' : $this->translateCode($metaInfo['department'], 'department'),
            $perfix . '_user_id' => $metaInfo['user_id'],
            $perfix . '_username' => $this->getUserName($metaInfo['user_id']),
        ];
    }

    private function getUserName($id)
    {
        if (empty($id)) {
            return '';
        }
        $user = User::findOne(['id' => $id]);
        return $user->username ?? '';
    }

    public function getForm($formId)
    {
        $params = $this->genSignParams();
        $params['id'] = $formId;
        $url = $this->host . self::EVENT_FORM_PATH;
        $response = Yii::$app->curl->get($url, $params);

        return $response['data'];
    }
}
